/****************************************************************************
 * Copyright 2009 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.jumpstart.graphics;

import static com.gotobject.jumpstart.graphics.PlatformUtils.isMac;
import static com.gotobject.jumpstart.graphics.WindowUtils.makeSpacer;
import static com.gotobject.jumpstart.graphics.WindowUtils.isParentWindowFocused;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class CommandDisplay {
    private final   JDialog         view;
    private         JComponent      contentPane;
    private final   TitlePanel      titlePanel;
    private final   MainPanel       mainPanel;
    private final   CommandLine     commandLine;
    private final   BottomPanel     bottomPanel;

    private static final int ROUNDED_RECT_DIAMETER      = 16;
    private static final int SPACE_BEGIN_COMMAND        = 10;

    /**
     * Creates a command display.
     */
    public CommandDisplay() {
        this("");
    }

    /**
     * creates a command display.
     * @param owner
     *      the {@link Frame} that a command window is parented to. Can be null.
     */
    public CommandDisplay(JFrame owner){
        this("", owner);
    }

    /**
     * Creates a command display.
     *
     * @param title the title to use for this display.
     */
    public CommandDisplay(String title) {
        this(title, null);
    }

    /**
     * Creates a command display.
     *
     * @param title
     *      the title to use for a command window.
     * @param owner
     *      the {@link Frame} that a command window is parented to. Can be null.
     */
    public CommandDisplay(String title, Frame owner) {
        view            = new JDialog(owner);
        view.setTitle(title);

        mainPanel   = MainPanel.emptyPanel();
        titlePanel  = TitlePanel.makeNewTitlePanel(title, makeCloseButtonActionListener());
        commandLine = CommandLine.emptyCommandLine();
        bottomPanel = BottomPanel.makeNewBottomPanel(view);

        layoutComponents();
    }


    /**
     * gets the {@link JDialog} backing this {@code CommandWindow}.
     * @return 
     *      the {@code JDialog} backing this {@code CommandWindow}.
     */
    public JDialog commandWindow() {
        return view;
    }


    public JDialog commandWindowTest(){
        final JDialog r = commandWindow();
        r.setLocationRelativeTo(null);
        r.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        r.setVisible(true);
        return r;
    }

    /**
     * adds some command text to the window.
     * @param text
     *      command text
     */
    public void enterCommand(String text){
        commandLine.add("".equals(text) ? " " : text);
    }

    /**
     * Gets the {@link JComponent} to add content to.
     *
     * @return the container to add content to.
     */
    public JComponent getContentPane() {
        return contentPane;
    }

    /**
     * hides the close button on this command window's title bar.
     */
    public void hideCloseButton() {
        titlePanel.hideCloseButton();
    }

    /**
     * hides the title panel on the command window.
     */
    public void hideTitle(){
        titlePanel.setVisible(false);
        mainPanel.repaint();
    }

    @SuppressWarnings({"deprecation"})
    private void layoutComponents() {
        // indicate that this frame should not make all the content draggable. by default, when you
        // set the opacity to 0 (like we do below) this property automatically gets set to true.
        // also note that this client property must be set *before* changing the opacity (not sure
        // why).
        view.getRootPane().putClientProperty("apple.awt.draggableWindowBackground", Boolean.FALSE);
        view.setUndecorated(true);

        WindowUtils.makeWindowNonOpaque(view);
        // for Java 5 on platforms other than Mac (those that don't support transparency), it looks
        // nicer to use a black background rather than the default (usually white).
        view.getRootPane().setBackground(Color.BLACK);

        mainPanel.add(titlePanel, BorderLayout.NORTH);

        // set the backing frame's content pane.
        view.setContentPane(mainPanel);
        // set the command panel's content pane to a blank JPanel by default.
        JPanel panel = new JPanel();
        panel.setOpaque(false);
        setContentPane(panel);

        // Create a horizontal panel
        mainPanel.add(commandLine.interactsWith(view), BorderLayout.CENTER);

        // listen to the frame's title property so that we can update the label rendering the title.
        view.addPropertyChangeListener("title", makeTitlePropertyChangeListener());

        WindowUtils.createAndInstallRepaintWindowFocusListener(view);
        new WindowDragger(view, titlePanel);
    }

    /**
     * make a new rounded-corner panel.
     * @return
     *      a new {@link JPanel} object.
     */
    public static JPanel makeRoundedCornerPanel(){
        return MainPanel.emptyPanel();
    }

    /**
     * make a new auto-resize text field.
     * @return
     *      a new {@link JTextField} object.
     */
    public static JTextField makeAutoResizeTextField(){
        return new ResizableText();
    }

    /**
     * @return a new {@link ActionListener} object, which
     *         simulate clicking the "real" close button on a window.
     */
    private ActionListener makeCloseButtonActionListener() {
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // simulate clicking the "real" close button on a window.
                view.dispatchEvent(new WindowEvent(view, WindowEvent.WINDOW_CLOSING));
            }
        };
    }

    /**
     * makes this panel resizeable. Note that there is currently a bug in the Mac JRE which causes a very bad flicker when
     * a window is programmatically resized. For now, it's suggested that you do not use this method unless you are on a
     * non-Mac platform.
     */
    public void makeResizeable() {
        mainPanel.add(bottomPanel, BorderLayout.SOUTH);
    }

    /**
     * @return a new {@link PropertyChangeListener} object which
     *      communicates changes to the title panel on the window.
     */
    private PropertyChangeListener makeTitlePropertyChangeListener() {
        return new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                titlePanel.setTitle(view.getTitle());
            }
        };
    }

    /**
     * shows the title panel on this command window.
     */
    public void showTitle(){
        titlePanel.setVisible(true);
        mainPanel.repaint();
    }


    /**
     * sets the {@link JComponent} to use as the container for this {@code CommandWindow}'s content.
     *
     * @param contentPane
     *      the container for this {@code CommandWindow}'s content.
     */
    public void setContentPane(JComponent contentPane) {
        // remove the old content pane if there was one.
        if (this.contentPane != null) {
            mainPanel.remove(this.contentPane);
        }

        this.contentPane = contentPane;
        mainPanel.add(this.contentPane, BorderLayout.CENTER);
    }


    /**
     * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
     */
    private static class MainPanel extends JPanel {
        private static final Color  HIGHLIGHT               = new Color(30, 30, 30, 216);//new Color(255, 255, 255, 59);
        private static final Color  HIGHLIGHT_BOTTOM        = new Color(30, 30, 30, 216);//new Color(255, 255, 255, 25);
        private static final Color  BACKGROUND              = new Color(30, 30, 30, 216);

        /**
         * creates a rounded corner panel.
         */
        private MainPanel() {
            setLayout(new BorderLayout());
            setOpaque(false);
        }

        /**
         * create an empty rounded corner panel.
         * @return
         *      a new {@link CommandDisplay.MainPanel panel}.
         */
        public static MainPanel emptyPanel(){
            return new MainPanel();
        }


        @Override
        protected void paintBorder(Graphics g) {
            // create a copy of the graphics object and turn on anti-aliasing.
            final Graphics2D graphics2d = (Graphics2D) g.create();
            graphics2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON
            );

            // paint a border around the window that fades slightly to give a more pleasant highlight
            // to the window edges.
            GradientPaint paint = new GradientPaint(
                    0,
                    0,
                    HIGHLIGHT,
                    0,
                    getHeight(),
                    HIGHLIGHT_BOTTOM
            );

            graphics2d.setPaint(paint);
            graphics2d.drawRoundRect(
                    0,
                    0,
                    getWidth() - 1,
                    getHeight() - 1,
                    ROUNDED_RECT_DIAMETER,
                    ROUNDED_RECT_DIAMETER
            );

            graphics2d.dispose();
        }

        @Override
        protected void paintComponent(Graphics g) {
            // create a copy of the graphics object and turn on anti-aliasing.
            final Graphics2D graphics2d = (Graphics2D) g.create();
            graphics2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON
            );

            graphics2d.setComposite(AlphaComposite.Src);

            // draw the rounded rectangle background of the window.
            graphics2d.setColor(BACKGROUND);
            graphics2d.fillRoundRect(
                    0,
                    0,
                    getWidth(),
                    getHeight(),
                    ROUNDED_RECT_DIAMETER,
                    ROUNDED_RECT_DIAMETER
            );

            // tell the shadow to revalidate.
            getRootPane().putClientProperty("apple.awt.windowShadow.revalidateNow", new Object());
            // we have abused the graphics2d already, so let's dispose it.
            graphics2d.dispose();
        }

    }

    /**
     *  represents the footer of the CommandWindow.
     */
    private static class BottomPanel extends JPanel {
        private static final Icon RESIZE_ICON = new ImageIcon(TitlePanel.class.getResource("/com/gotobject/jumpstart/resources/images/resize_corner_dark.png"));

        private final   Window  window;
        private         int     xOffsetToWindowEdge;
        private         int     yOffsetToWidnowEdge;
        private final   JLabel  resizeCorner            = new JLabel(RESIZE_ICON);

        /**
         * creates teh footer of the command window.
         * @param window
         *      command window
         */
        private BottomPanel(Window window) {
            super(new FlowLayout(SwingConstants.RIGHT));
            this.window = window;
            setOpaque(false);
            add(resizeCorner);
            resizeCorner.addMouseListener(createMouseListener());
            resizeCorner.addMouseMotionListener(createMouseMotionListener());
        }

        private MouseAdapter createMouseListener() {
            return new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    final Point windowPoint = SwingUtilities.convertPoint(resizeCorner, e.getPoint(), window);
                    xOffsetToWindowEdge = window.getWidth() - windowPoint.x;
                    yOffsetToWidnowEdge = window.getHeight() - windowPoint.y;
                }
            };
        }

        private MouseMotionListener createMouseMotionListener() {
            return new MouseMotionAdapter() {
                @Override
                public void mouseDragged(MouseEvent e) {
                    final Point windowPoint = SwingUtilities.convertPoint(resizeCorner, e.getPoint(), window);
                    window.setSize(windowPoint.x + xOffsetToWindowEdge, windowPoint.y + yOffsetToWidnowEdge);

                    // the following two lines are a work-around to Sun bug 6318144:
                    // http://bugs.sun.com/view_bug.do;?bug_id=6318144
                    window.invalidate();
                    window.validate();
                }
            };
        }

        /**
         * creates teh footer of the command window.
         * @param owner
         *      command window
         * @return a new bottom panel.
         */
        public static BottomPanel makeNewBottomPanel(Window owner){
            return new BottomPanel(owner);
        }

    }



    /**
     * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
     */
    private static class ResizableText extends JTextField implements DocumentListener {
        private static final int MIN_FONT_SIZE = 10;
        private static final int MAX_FONT_SIZE = 45;

        private ResizableText() {
            super();
            styleTextField();
        }

        private ResizableText(String text) {
            super(text);
            styleTextField();
        }

        public static ResizableText emptyTextField(){
            return new ResizableText();
        }

        public static ResizableText textFieldWith(String text){
            return new ResizableText(text);
        }

        public static ResizableText copyOf(JTextField src){
            return new ResizableText(src.getText());
        }


        @Override
        public void addNotify() {
            super.addNotify ();
            getDocument().addDocumentListener(this);
            scalingOutward();
        }

        public void changedUpdate(DocumentEvent e) {
            System.out.println(String.format("update event ->%s", e));
        }


        public static Container findParentThenUseItsDimentions(JComponent textField){
            Container parent = textField.getParent();
            Container result = null;
            do {
                if(parent.getParent() == null){
                    result = parent;
                    parent = parent.getParent();
                }
//                else if(parent instanceof JDialog){
//                    result = parent;
//                    parent = null;
//                }
                else {
                    parent = parent.getParent();
                }
            } while(parent != null);
            return result;
        }


        public void insertUpdate(DocumentEvent e) {
            scalingOutward();
        }


        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(getBackground()); // remove after testing its removal impact
            setBorder(new EmptyBorder(0,0,0,0));
            g.fillRect(0,0, getWidth(),getHeight());
            super.paintComponent(g);
        }

        public void removeUpdate(DocumentEvent e) {
            scalingInward();
        }


        @Override
        public void removeNotify() {
            super.removeNotify();
            getDocument().removeDocumentListener(this);
        }

        private void scalingOutward(){
            final Graphics2D g = (Graphics2D) getGraphics();
            final FontMetrics theFontMetrics = g.getFontMetrics();
            final Rectangle2D theBounds = theFontMetrics.getStringBounds(getText(), g);

            final Container parent = findParentThenUseItsDimentions(this);
            final Font original         = getFont();
            final int  originalSize     = original.getSize();
            final int  scaleOutFactor   = MIN_FONT_SIZE - ((int)theBounds.getWidth() - originalSize)/72;
            final int  scaleOutTotal    = originalSize - scaleOutFactor;

            if(theBounds.getWidth() + MIN_FONT_SIZE > parent.getWidth() && parent.getWidth() > 0){
                if(scaleOutTotal >= MIN_FONT_SIZE && scaleOutFactor < MAX_FONT_SIZE){
                    setFont(new Font(original.getName(), original.getStyle(), scaleOutTotal));
                } else {
                    System.out.println("out of whack");
                }
            }

            setPreferredSize(new Dimension((int)Math.max (theBounds.getWidth(), MIN_FONT_SIZE), (int)theBounds.getHeight()));
            revalidate();
            repaint();
        }

        private void scalingInward(){
            final Graphics2D g = (Graphics2D) getGraphics();
            final FontMetrics theFontMetrics = g.getFontMetrics();
            final Rectangle2D theBounds = theFontMetrics.getStringBounds(getText(), g);

            final Container parent = findParentThenUseItsDimentions(this);
            final Font original             = getFont();
            final int  originalSize         = original.getSize();
            final int  scaleInFactor        = "".equals(getText()) ? MAX_FONT_SIZE : (MIN_FONT_SIZE - ((int)theBounds.getWidth() - originalSize)/72);
            final int  scaleInTotal         = "".equals(getText()) ? MAX_FONT_SIZE : originalSize + scaleInFactor;

            if(theBounds.getWidth() + SPACE_BEGIN_COMMAND <= parent.getWidth() && parent.getWidth() > 0){
                if(scaleInTotal <= MAX_FONT_SIZE ){
                    setFont(new Font(original.getName(), original.getStyle(), scaleInTotal));
                }
            }

            setPreferredSize(new Dimension((int)Math.max (theBounds.getWidth(), MIN_FONT_SIZE), (int)theBounds.getHeight()));
            revalidate();
            repaint();
        }


        private void styleTextField(){
            styleTextField(
                    Color.WHITE,
                    new Color(0,0,0,0),
                    new Color(255,255,255,250),
                    new Color(255,255,255),
                    new Color(0,0,0)
            );
        }

        private void styleTextField(Color foreground, Color background, Color caret, Color selection, Color selectedText){
            setOpaque(false);
            setFont(new Font("Marydale", Font.PLAIN, MAX_FONT_SIZE));
            setForeground(foreground);
            setBackground(background);
            setCaretColor(caret);
            setSelectionColor(selection);
            setSelectedTextColor(selectedText);
        }
    }    

    /**
     * Represents the title panel of the {@code CommandWindow}.
     */
    private static class TitlePanel extends JPanel {
        private static final Color FONT_COLOR               = new Color(255, 255, 255, 255);
        private static final Color UNFOCUSED_FONT_COLOR     = new Color(0xcccccc);
        private static final Color HIGHLIGHT                = new Color(0, 0, 0, 10);//new Color(255, 255, 255, 25);
        private static final Color TOP_BACKGROUND_TOP       = new Color(255, 255, 255, 59);
        private static final Color TOP_BACKGROUND_BOTTOM    = new Color(196, 196, 196, 59);
        private static final Color BOTTOM_BACKGROUND        = new Color(255, 255, 255, 30);
        private static final Color UNFOCUSED_BACKGROUND     = new Color(0, 0, 0, 10);

        private static final Icon CLOSE_ICON            = new ImageIcon(TitlePanel.class.getResource("/com/gotobject/jumpstart/resources/images/close.png"));
        private static final Icon CLOSE_HOVER_ICON      = new ImageIcon(TitlePanel.class.getResource("/com/gotobject/jumpstart/resources/images/close_hover.png"));
        private static final Icon CLOSE_PRESSED_ICON    = new ImageIcon(TitlePanel.class.getResource("/com/gotobject/jumpstart/resources/images/close_pressed.png"));

        private final JButton       closeButton     = new JButton(CLOSE_ICON);
        private final JComponent    spacer;
        private final JLabel        label;
        private final AtomicBoolean isShinyTitle    = new AtomicBoolean();


        /**
         * Create a TitlePanel object.
         * @param title
         *      title of the {@code CommandWindow}
         * @param closeButtonActionListener
         *      close button's action listener.
         */
        private TitlePanel(String title, ActionListener closeButtonActionListener) {
            label = new JLabel(title, SwingConstants.CENTER);
            label.setFont(label.getFont().deriveFont(Font.BOLD, 11.0f));

            setOpaque(false);
            setPreferredSize(new Dimension(-1, 20));
            updateFocusState();

            closeButton.setBorder(getCloseButtonBorder());
            closeButton.setVerticalAlignment(SwingConstants.CENTER);
            closeButton.setOpaque(false);
            closeButton.setFocusable(false);
            closeButton.setBorderPainted(false);
            closeButton.setContentAreaFilled(false);
            closeButton.setRolloverIcon(CLOSE_HOVER_ICON);
            closeButton.setPressedIcon(CLOSE_PRESSED_ICON);
            closeButton.addActionListener(closeButtonActionListener);

            spacer = makeSpacer(closeButton.getPreferredSize().width, 0);

            setLayout(new BorderLayout());
            add(label, BorderLayout.CENTER);
            add(closeButton, isMac() ? BorderLayout.WEST : BorderLayout.EAST);
            add(spacer, isMac() ? BorderLayout.EAST : BorderLayout.WEST);
        }

        /**
         * Create a TitlePanel object.
         * @param title
         *      title of the {@code CommandWindow}
         * @param closeButtonActionListener
         *      close button's action listener.
         * @return a new title panel.
         */
        public static TitlePanel makeNewTitlePanel(String title, ActionListener closeButtonActionListener){
            return new TitlePanel(title, closeButtonActionListener);
        }

        /**
         *
         */
        void makeShiny(){
            isShinyTitle.set(true);
            // todo do we need to do this?
            repaint();
        }

        /**
         *
         */
        void makeFlat(){
            isShinyTitle.set(false);
            // todo do we need to do this?
            repaint();

        }

        /**
         * @return {@code true} of the title panel is shiny. false otherwise.
         */
        boolean isShiny(){
            return isShinyTitle.get();
        }

        /**
         * hind the close button on the title panel.
         */
        private void hideCloseButton() {
            closeButton.setVisible(false);
            spacer.setVisible(false);
        }

        /**
         * @return a new {@link Border} object which changes
         *      the border of the close button.
         */
        private Border getCloseButtonBorder() {
            return isMac()
                    ? BorderFactory.createEmptyBorder(0, 5, 0, 0)
                    : BorderFactory.createEmptyBorder(0, 0, 0, 5);
        }

        /**
         * @param title
         *      text to be displayed on the title panel on
         *      the window.
         */
        private void setTitle(String title) {
            label.setText(title);
        }

        /**
         *
         */
        private void updateFocusState() {
            Boolean focused = isParentWindowFocused(this);
            label.setForeground(focused == null || focused ? FONT_COLOR : UNFOCUSED_FONT_COLOR);
        }

        @Override
        protected void paintComponent(Graphics g) {
            // create a copy of the graphics object and turn on anti-aliasing.
            Graphics2D graphics2d = (Graphics2D) g.create();
            graphics2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON
            );

            // calculate the point in the title bar at which to change the background color.
            int midPointY = ROUNDED_RECT_DIAMETER / 2 + 3;

            // if the window has focus, draw a shiny title bar.
            // else draw a flat background.
            if (isParentWindowFocused(this) && isShiny()) {
                // 1. The top half --------------------------------------------------------------//
                // create and set the shiny paint.
                GradientPaint paint = new GradientPaint(
                        0,
                        0,
                        TOP_BACKGROUND_TOP,
                        0,
                        midPointY,
                        TOP_BACKGROUND_BOTTOM
                );
                graphics2d.setPaint(paint);
                // create a rounded rectangle area as big as the entire title bar, then subtract
                // off the bottom half (roughly) in order to have perfectly square edges.
                Area titleArea = new Area(
                        new Area(
                                new RoundRectangle2D.Double(
                                        0,
                                        0,
                                        getWidth(),
                                        getHeight(),
                                        ROUNDED_RECT_DIAMETER,
                                        ROUNDED_RECT_DIAMETER
                                )
                        )
                );

                titleArea.subtract(
                        new Area(
                                new Rectangle(
                                        0,
                                        midPointY,
                                        getWidth(),
                                        midPointY
                                )
                        )
                );

                // draw the top half of the title bar (the shine).
                graphics2d.fill(titleArea);
                // 2. The bottom half -----------------------------------------------------------//
                // draw the bottom half of the title bar.
                int bottomHeight = getHeight() - midPointY;
                graphics2d.setColor(BOTTOM_BACKGROUND);
                graphics2d.fillRect(0, midPointY, getWidth(), bottomHeight);
            } else {
                // create an area that has rounded corners at the top and square corners at the
                // bottom.
                graphics2d.setColor(UNFOCUSED_BACKGROUND);                
                Area titleArea = new Area(
                        new Area(
                                new RoundRectangle2D.Double(
                                        0,
                                        0,
                                        getWidth(),
                                        getHeight(),
                                        ROUNDED_RECT_DIAMETER,
                                        ROUNDED_RECT_DIAMETER
                                )
                        )
                );

                titleArea.subtract(
                        new Area(
                                new Rectangle(
                                        0,
                                        midPointY,
                                        getWidth(),
                                        midPointY
                                )
                        )
                );
                graphics2d.fill(titleArea);
                graphics2d.setColor(HIGHLIGHT);
                graphics2d.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
            }

            graphics2d.dispose();
        }
    }

    /**
     * Command Line builder that represents a command line entry provided
     * by a user.
     */
    private static class CommandLine {
        private final ResizableText textField;
        private final Box           horizontalBox;
        private static final DocumentListener NULL_DOC_LISTENER = new DocumentListener(){
            public void insertUpdate(DocumentEvent e) {}
            public void removeUpdate(DocumentEvent e) {}
            public void changedUpdate(DocumentEvent e){}
        };

        /**
         * creates a {@code CommandLine} entry.
         * @param text
         *      auto resized and transluncent text field.
         * @param resizeDialogDocumentListener
         *      document listener that will allow the dialog
         *      to grow/shrink given the size of a text field.
         */
        private CommandLine(String text, DocumentListener resizeDialogDocumentListener){
            textField       = ResizableText.textFieldWith(text);
            textField.getDocument().addDocumentListener(resizeDialogDocumentListener);
            horizontalBox   = Box.createHorizontalBox();
            horizontalBox.add(Box.createHorizontalStrut(SPACE_BEGIN_COMMAND));
        }

        /**
         *  creates a command line entry.
         */
        private CommandLine(){
            this("", NULL_DOC_LISTENER);
        }

        /**
         * add new command line text.
         * @param text
         *      new command line text.
         */
        public void add(String text){
            textField.setText(text);
        }


        /**
         *  creates a command line entry.
         * @return a new command line.
         */
        public static CommandLine emptyCommandLine(){
            return new CommandLine();
        }

        /**
         * update command line.
         * @param text
         *      new text
         * @param owner
         *      owner of the panel
         * @return
         *      updated command line.
         */
        public JComponent updateCommandLine(String text, Window owner){
            textField.setText(text);
            textField.getDocument().removeDocumentListener(NULL_DOC_LISTENER);
            textField.getDocument().addDocumentListener(makeDocumentListener(owner));
            horizontalBox.add(textField);
            return horizontalBox;
        }


        /**
         * define a contract between the window (owner) and the
         * command line.
         * @param owner
         *      a {@link JDialog} or {@link Window}
         * @return
         *      a {@link JComponent} representing a horizontal panel.
         */
        public JComponent interactsWith(Window owner){
            return updateCommandLine("", owner);
        }


        /**
         * make a new document listener that will update the size of the JDialog.
         * @param owner
         *      panel which contains the text field
         * @return
         *      a new {@link DocumentListener} object.
         */
        private DocumentListener makeDocumentListener(final Window owner){
            return new DocumentListener(){
                final AtomicInteger dialogOriginalWidth         = new AtomicInteger();      // original width of view
                final AtomicInteger dialogWidthBeforeShrinking  = new AtomicInteger();     //  the width just before start shrinking

                @Override
                public void insertUpdate(DocumentEvent e) {
                    final Graphics2D    graphics2D   = (Graphics2D) textField.getGraphics();
                    final FontMetrics   fontMetrics  = graphics2D.getFontMetrics();
                    final int width     = fontMetrics.stringWidth(textField.getText());
                    final int height    = fontMetrics.getHeight();

                    System.out.println(String.format("width %d, heig %d", width, height));

                    if(owner.getWidth() == 0 && owner.getHeight() == 0){
                        // find a better way to determine the height increment, instead of 24
                        owner.setSize(width + (SPACE_BEGIN_COMMAND * 5), height + 24);
                        dialogOriginalWidth.compareAndSet(0, width + (SPACE_BEGIN_COMMAND * 5));
                        owner.invalidate();
                        owner.validate();
                        return;
                    }

                    final Container parent      = owner.getParent();
                    final int       parentWidth = parent == null ? 0 : parent.getWidth();
                    if(owner.getWidth() < parentWidth) {
                        final int widthIncrement    = Math.abs(owner.getWidth() - SPACE_BEGIN_COMMAND + width)/72;
                        final int augmentedWidth = width + SPACE_BEGIN_COMMAND + widthIncrement;
                        System.out.println(String.format("the increment factor is %d, but text width is %d and parent width is %d", widthIncrement, width, owner.getWidth()));
                        System.out.println(String.format("dialog size is %d", augmentedWidth));
                        dialogOriginalWidth.compareAndSet(0, augmentedWidth);
                        owner.setSize(Math.min(augmentedWidth, parentWidth), owner.getHeight());
                        owner.invalidate();
                        owner.validate();
                    }
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    final Graphics2D    graphics2D   = (Graphics2D) textField.getGraphics();
                    final FontMetrics   fontMetrics  = graphics2D.getFontMetrics();

                    final int width     = fontMetrics.stringWidth(textField.getText());
                    final int height    = fontMetrics.getHeight();

                    System.out.println(String.format("width %d, height %d original width %d", width, height, dialogOriginalWidth.get()));

                    if(owner.getWidth() > dialogOriginalWidth.get()){
                        final int scaleInFactor     = Math.abs(owner.getWidth() - (width + SPACE_BEGIN_COMMAND));
                        final int total             = (owner.getWidth() - scaleInFactor);
                        owner.setSize(Math.max(total, width), owner.getHeight());                        
                        System.out.println(String.format("width %d total %d", Math.max(total, width), total));
                        owner.invalidate();
                        owner.validate();
                    }
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    System.out.println(String.format("update event ->%s", e));
                }
            };
        }

        /**
         * @return
         *      a new resizable text field.
         */
        JTextField getResizableText(){
            return ResizableText.copyOf(textField);
        }

        @Override
        public String toString() {
            return new StringBuilder(textField.getText()).toString();
        }
    }
}
